package com.triplelink.builder.chain;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Stack;

import com.triplelink.annotations.ObjectProperty;
import com.triplelink.annotations.Property;
import com.triplelink.builder.query.QueryTL;
import com.triplelink.to.steak.InstanceBlockTO;

public class InstanceBlockTOBuilder
{
	public static InstanceBlockTO build(Class<?> entityClass, String rootLabelId, QueryTL resources)
	{
		InstanceBlockTO itemBlockMain = new InstanceBlockTO(rootLabelId, entityClass);
		
		Stack<InstanceBlockTO> itensBlocksCicleds = new Stack<InstanceBlockTO>();
		
		fill(itemBlockMain, itensBlocksCicleds, resources);
		buildCicledEdges(itensBlocksCicleds, resources);
		
		return itemBlockMain;
	}
	
	private static void fill(InstanceBlockTO itemBlockMain, Stack<InstanceBlockTO> itensBlocksCicleds, QueryTL resources)
	{
		Stack<InstanceBlockTO> stackBuilder = new Stack<InstanceBlockTO>();
		
		stackBuilder.push(itemBlockMain);
		
		while (!stackBuilder.isEmpty())
		{
			InstanceBlockTO itemBlock = stackBuilder.pop();
			
			Class<?> currentClass = itemBlock.getEntityClass();
			
			while (currentClass != null)
			{
				for (Field field : currentClass.getDeclaredFields())
				{
					putChildObject(itemBlock, field, stackBuilder, itensBlocksCicleds, resources);
				}
				
				currentClass = currentClass.getSuperclass();
			}
		}
	}
	
	private static void putChildObject(InstanceBlockTO owner, Field field, Stack<InstanceBlockTO> stackBuilder, Stack<InstanceBlockTO> itensBlocksCicleds, QueryTL resources)
	{
		ObjectProperty join = field.getAnnotation(ObjectProperty.class);
		
		if (join == null)
			return;
		
		Property property = field.getAnnotation(Property.class);
		
		if (property == null)
			return;
		
		Class<?> entityClass = field.getType();
		
		if (Collection.class.isAssignableFrom(entityClass) && field.getGenericType() instanceof ParameterizedType)
		{
			entityClass = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
		}
		
		InstanceBlockTO item = new InstanceBlockTO(resources.createNewLabel(), owner, property.uri(), entityClass);
		
		owner.putObject(property.uri(), item);
		
		InstanceBlockTO leftEnd = getLeftEnd(item);
		
		if (leftEnd != null)
		{
			putCicle(item, leftEnd);
			
			if (!itensBlocksCicleds.contains(leftEnd))
				itensBlocksCicleds.push(leftEnd);
		}
		else
		{
			stackBuilder.push(item);
		}
	}
	
	private static InstanceBlockTO getLeftEnd(InstanceBlockTO item)
	{
		InstanceBlockTO currentItem = item.getOwner();
		
		while (currentItem != null)
		{
			if (currentItem.getEntityClass() == item.getEntityClass())
				return currentItem;
			
			currentItem = currentItem.getOwner();
		}
		
		return null;
	}
	
	private static void putCicle(InstanceBlockTO item, InstanceBlockTO leftEnd)
	{
		if (leftEnd.getCiclesStacks() == null)
			leftEnd.setCiclesStacks(new ArrayList<Stack<String>>());
			
		Stack<String> ciclesUniqueType = new Stack<String>();
		
		ciclesUniqueType.push(item.getOwnerProperty());
		
		InstanceBlockTO currentItem = item.getOwner();
		
		while (currentItem.getEntityClass() != item.getEntityClass())
		{
			ciclesUniqueType.push(currentItem.getOwnerProperty());
			currentItem = currentItem.getOwner();
		}
		
		leftEnd.getCiclesStacks().add(ciclesUniqueType);
	}
	
	private static void buildCicledEdges(Stack<InstanceBlockTO> itensBlocksCicleds, QueryTL resources)
	{
		while (!itensBlocksCicleds.isEmpty())
		{
			InstanceBlockTO itemBlockCicled = itensBlocksCicleds.pop();
			
			StringBuilder cicleEdge = new StringBuilder();
			
			itemBlockCicled.setCicleEdge(cicleEdge);
			itemBlockCicled.setEndCicleLabelId(resources.createNewLabel());
			
			cicleEdge.append("(");
			
			for (Stack<String> stackCicle : itemBlockCicled.getCiclesStacks())
			{
				InstanceBlockTO currentItemBlockPath = itemBlockCicled;
				
				while (!stackCicle.isEmpty())
				{
					String property = stackCicle.pop();
					
					cicleEdge.append("<").append(property).append(">/");
					
					currentItemBlockPath = currentItemBlockPath.getObjects().get(property);
					
					if (currentItemBlockPath != null && currentItemBlockPath.getCicleEdge() != null)
						cicleEdge.append(currentItemBlockPath.getCicleEdge()).append("/");
				}
				
				cicleEdge
				.deleteCharAt(cicleEdge.length() - 1)
				.append("|");
			}
			
			cicleEdge
			.deleteCharAt(cicleEdge.length() - 1)
			.append(")*");
			
			itemBlockCicled.setCiclesStacks(null);
		}
	}
}